package com.example.util;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;

/**
 * @Author: LiuLei
 * @Description: SM2工具类
 * @Date: 2023/9/15  17:38
 * @Version: 1.0
 */
@Slf4j
@Component
public class Sm2Utils {
    /**
     * 编码方式
     */
    private static final String ENCODING = "UTF-8";
    private static String PUBLIC_KEY = "023ea5ca7ce135b2a841aa2bdc94afa03be404bcd0f736f656316bfe662e20ce73";
    private static String PRIVATE_KEY = "1ce899d76482f35a0685d5bb358ed396d882adde9b583853925f9b154fca84c7";


    /**
     * SM2 生成公私钥
     *
     * @return 公私钥
     */
    public static void generateKeyPair() {
        try {
            BouncyCastleProvider provider = new BouncyCastleProvider();

            // 获取椭圆曲线相关生成参数规格
            ECGenParameterSpec genParameterSpec = new ECGenParameterSpec("sm2p256v1");
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", provider);

            // 使用SM2的算法区域初始化密钥生成器
            keyPairGenerator.initialize(genParameterSpec, new SecureRandom());

            // 生成密钥对
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            BCECPrivateKey exPrivateKey = (BCECPrivateKey) keyPair.getPrivate();
            BCECPublicKey ecPublicKey = (BCECPublicKey) keyPair.getPublic();

            // 解密密钥
            BigInteger privateKey = exPrivateKey.getD();
            // 加密密钥
            ECPoint publicKey = ecPublicKey.getQ();
            System.out.println("私钥：");
            System.out.println(privateKey.toString(16));
            System.out.println("公钥：");
            System.out.println(new String(Hex.encode(publicKey.getEncoded(true))));
        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加密
     * C1C2C3
     * @param source
     * @return
     */
    public static String encrypt(String source) {
        String resultStr = null;
        BouncyCastleProvider provider = new BouncyCastleProvider();
        // 获取SM2相关参数
        X9ECParameters parameters = GMNamedCurves.getByName("sm2p256v1");
        // 椭圆曲线参数规格
        ECParameterSpec ecParameterSpec = new ECParameterSpec(parameters.getCurve(), parameters.getG(), parameters.getN(), parameters.getH());
        // 将公钥HEX字符串转换为椭圆曲线对应的点
        ECPoint ecPoint = parameters.getCurve().decodePoint(Hex.decode(PUBLIC_KEY));
        try {
            byte[] srcData = source.getBytes(ENCODING);
            // 获取椭圆曲线KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
            // 将椭圆曲线点转为公钥KEY对象
            BCECPublicKey bcecPublicKey = (BCECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, ecParameterSpec));
            // 获取SM2加密器
            Cipher cipher = Cipher.getInstance("SM2", provider);
            // 初始化为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, bcecPublicKey);
            byte[] result = cipher.doFinal(srcData);
            // 加密并编码为base64格式
            resultStr = Base64.getEncoder().encodeToString(result);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return resultStr;
    }


    /**
     * SM2加密算法
     *
     * @param source 明文数据
     * @return
     */
    public static String encrypt2(String source) {
        // 获取一条SM2曲线参数
        X9ECParameters sm2Parameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2Parameters.getCurve(), sm2Parameters.getG(), sm2Parameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2Parameters.getCurve().decodePoint(Hex.decode(PUBLIC_KEY));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        ParametersWithRandom parametersWithRandom = new ParametersWithRandom(publicKeyParameters, new SecureRandom());
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
        // 设置sm2为加密模式
        sm2Engine.init(true, parametersWithRandom);
        String resultStr = null;
        try {
            byte[] srcData = source.getBytes(ENCODING);
            byte[] result = sm2Engine.processBlock(srcData, 0, srcData.length);
            // 加密并编码为base64格式
            resultStr = Base64.getEncoder().encodeToString(result);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return resultStr;

    }

    /**
     * 解密
     * C1C2C3
     * @param source
     * @return
     */
    public static String decrypt(String source) {
        String resultStr = null;
        BouncyCastleProvider provider = new BouncyCastleProvider();
        // 获取SM2相关参数
        X9ECParameters parameters = GMNamedCurves.getByName("sm2p256v1");
        // 椭圆曲线参数规格
        ECParameterSpec ecParameterSpec = new ECParameterSpec(parameters.getCurve(), parameters.getG(), parameters.getN(), parameters.getH());
        // 将私钥HEX字符串转换为X值
        BigInteger bigInteger = new BigInteger(PRIVATE_KEY, 16);
        try {
            //内容转byte数组
            byte[] srcData = Base64.getDecoder().decode(source);
            // 获取椭圆曲线KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
            // 将X值转为私钥KEY对象
            BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyFactory.generatePrivate(new ECPrivateKeySpec(bigInteger, ecParameterSpec));
            // 获取SM2加密器
            Cipher cipher = Cipher.getInstance("SM2", provider);
            // 初始化为加密模式
            cipher.init(Cipher.DECRYPT_MODE, bcecPrivateKey);
            byte[] result = cipher.doFinal(srcData);
            // 解密
            resultStr = new String(result, ENCODING);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return resultStr;
    }

    /**
     * SM2解密算法
     *
     * @param source 密文数据
     * @return
     */
    public static String decrypt2(String source) {
        BigInteger privateKeyD = new BigInteger(PRIVATE_KEY, 16);
        //获取一条SM2曲线参数
        X9ECParameters sm2Parameters = GMNamedCurves.getByName("sm2p256v1");
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2Parameters.getCurve(), sm2Parameters.getG(), sm2Parameters.getN());
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
        // 设置sm2为解密模式
        sm2Engine.init(false, privateKeyParameters);
        String resultStr = null;
        try {
            //内容转byte数组
            byte[] srcData = Base64.getDecoder().decode(source);
            byte[] result = sm2Engine.processBlock(srcData, 0, srcData.length);
            // 解密
            resultStr = new String(result, ENCODING);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return resultStr;
    }
}